// #include <iostream>
// #include <thread>
// #include <unistd.h>

// using namespace std;

// void thread_func()
// {
//     while (true)
//     {
//         cout << "new thread..." << endl;
//         sleep(1);
//     }
// }

// int main()
// {
//     thread t1(thread_func);

//     while (true)
//     {
//         cout << "main thread..." << endl;
//         sleep(1);
//     }
//     t1.join();
//     return 0;
// }


#include <iostream>
#include <cassert>
#include <string>
#include <vector>
#include <pthread.h>
#include <unistd.h>

using namespace std;

// 我们先创建一个类，这个类可以让不同线程看到传过去的数据都是属于自己的
class ThreadData
{
    // 我们希望传什么数据就往里填
public:
    int number_id;    // 线程编号
    pthread_t id;
    char name[64];
};

class ThreadReturn
{
public:
    int exit_code;
    int exit_result;
};

// 1.这个start_routine现在被几个线程执行？10，那现在这个函数是什么状态？---> 重入状态
// 2.那这个函数是不是可重入函数吗？是的 (但是，严格来说不是，因为里面有cout，访问硬件了，往显示器打印，就有可能存在多个线程同时往一个显示器上写，产生数据不一致问题)
// 3.函数内部定义的变量，具有临时性，在多线程情况下，这个结论也适用，所以每一个线程都有自己独立的栈结构
void *start_routine(void *args)
{
    sleep(1);
    ThreadData* td = static_cast<ThreadData*>(args);
    int cnt = 10;
    while(cnt)
    {
        cout << "new thread, name : " << td->name << "; tid : " << td->id << "; args : " << args << "; cnt : " << cnt << "; &cnt : " << &cnt << endl;
        sleep(1);
        cnt--;
    }

    // delete td;                   // 我们不在这里释放，因为这个是主线程开辟的空间，让主线程自己去释放

    // return nullptr;              // 1.线程函数结束，return的时候，就算终止了
    // pthread_exit(nullptr);       // 2.调用库函数，参数就是我们等下要设置的退出结果，这里先设为nullptr

    // exit(0);                     // 可以用exit()吗？？不行！exit()是用来终止进程的，参考之前写的C/C++代码，malloc失败我们就退出进程

    // pthread_exit((void*)((long long)20240314));      // 纯数字，直接写到void*里面
    // return (void*)td->number_id;                    // 这里直接把number_id强制转换类型为void* ，假的地址也可以返回，那对象呢？也可以！

    // ThreadReturn *rt = new ThreadReturn();
    // rt->exit_code = 0;
    // rt->exit_result = 20240314;
    // return (void*)rt;

    // 注意：我们不能写成ThreadReturn rt;return (void*)&rt;这样是在创建局部变量,返回局部变量地址会出错的！

    return (void*)100;

}

int main()
{
    // 现在我想创建一批进程
    vector<ThreadData*> threads;
#define NUM 10
    // for (int i = 0; i < NUM; i++)
    // {
    //     pthread_t id;
    //     char buffername[64];
    //     snprintf(buffername, sizeof(buffername), "%s : %d", "new thread", i);
    //     pthread_create(&id, nullptr, start_routine, buffername);    // 注意：这里传过去的是缓冲区的地址
    //     // 今天我们创建了十个线程，有可能是主线程一直在创建，那十个线程一个都没跑起来，但是那个buffername已经被重复写了10次
    //     // 而传过去的又是缓冲区起始地址，每个线程去访问这个地址，那得到的数据都是主线程重复写了10次之后最新的数据
    //     // 所以不能这么搞
    //     // sleep(1);
    // }

    for (int i = 0; i < NUM; i++)
    {
        ThreadData *td = new ThreadData();
        td->number_id = i;
        snprintf(td->name, sizeof(td->name), "%s : %d", "new thread", i);
        pthread_create(&(td->id), nullptr, start_routine, td);    // 注意：这里传过去的是td里面的内容，这个内容就是堆区的地址，所以每次传过去的都是不一样的
        threads.push_back(td);

        cout << "&td : " << &td << endl;
        // 这里的&td每次都是一样的，因为这个函数体的栈结构是很稳定的，每一个给td创建空间，都是在上次的位置销毁后再次申请并覆盖
        // 这也是下面的例子的buffername为什么每次都在同一个地方销毁又创建

        // sleep(1);
    }

    // 线程也是需要被等待的
    // 获取线程退出结果(可以不关心)
    // 回收释放线程资源，避免内存泄漏
    // for(auto &iter : threads)
    // {
    //     // int n = pthread_join(iter->id, nullptr);

    //     // 如果我想获取退出结果呢
    //     // void *ret = nullptr;
    //     // int n = pthread_join(iter->id, &ret);

    //     ThreadReturn *rt = nullptr;
    //     int n = pthread_join(iter->id, (void**)&rt);
    //     // 线程退出的时候，为什么我们没有收到对应的退出信号呢？？？
    //     // 线程如果收到信号退出，那整个进程都退出了，连这个pthread_join都无法正常调用了，这个信号要放在哪里呢
    //     // 所以pthread_join就默认调用成功，不考虑线程出异常的问题，出异常是进程该考虑的

    //     assert(0 == n);

    //     // cout << "join success ! " << iter->name << endl;
    //     // cout << "join success ! " << "  retval :" << (long long)ret << endl;
    //     // cout << "join success ! " << "  number_id :" << (long long)ret << endl;

    //     cout << "join success !" << "exit_code : " << rt->exit_code << " ; exit_result : " << rt->exit_result << endl;
    //     delete iter;
    // }

    // 第三种线程终止的方法 -- pthread_cancel
    // 注意：线程要被取消，前提是这个线程已经跑起来了
    // 所以我们这里sleep(5);保证线程都跑起来了
    sleep(5);
    for(int i = 0; i < threads.size() / 2; i++)     // 取消一半
    {
        pthread_cancel(threads[i]->id);
        cout << "pthread cancel  : " << threads[i]->name << " success !" << endl;
        // 线程如果是被取消的 ---> 退出码是-1(PTHREAD_CANCELED)

    }

    for(auto &iter : threads)
    {
        void *ret = nullptr;

        int n = pthread_join(iter->id, &ret);
        assert(0 == n);
        (void)n;

        cout << "join success !" << "  exit_code : " << (long long)ret << endl;
        delete iter;
    }

    cout << "main thread quit!" << endl;

    /*
    for(auto &iter : threads)
    {
        cout << "td : " << iter << "; td->name : " << iter->name << "; td->id : " << iter->id << endl;
        // 这个td里面的值就是上面的args的值，是一样的
        // 这个for循环相当于主线程传过去的数据都能拿到
    }

    while(true)
    {
        cout << "main thread !" << endl;
        sleep(1);
    }
    */

    return 0;
}

// void *start_routine(void * args)
// {
//     // 一个线程如果出异常了，会影响其他进程吗? 会的(代码健壮性或者鲁棒性较差)
//     // 为什么?
//     // 信号是给进程(pid)发的，一旦我这个进程有哪一个线程出异常了，那就是我这个进程的问题
//     // OS就要给我这个进程发信号，并且终止，回收进程资源
//     // 那进程都被回收了，而线程的资源又是从进程来的，进程被回收了，线程也就被干掉了

//     // 或者也可以这样理解：每个线程的pid都是一样的，给这个pid发信号，所有线程都会收到

//     string name = static_cast<char*>(args);     // 安全的进行强制类型转换
//     while(true)
//     {
//         cout << "create success : " << name << endl;
//         sleep(1);

//         int *p = nullptr;
//         // p = nullptr;     // true
//         *p = 10;            // error
//     }
// }

// int main()
// {
//     pthread_t tid;
//     pthread_create(&tid, nullptr, start_routine, (void*)"thread one");
//     // 传统的一些函数是，成功返回0，失败返回-1，并且对全局变量errno赋值以指示错误。
//     // pthreads函数出错时不会设置全局变量errno（而大部分其他POSIX函数会这样做）。而是将错误代码通过返回值返回
//     // 如果设置了全局变量error，那么多个线程可能会同时访问，就会发生缺乏访问控制所带来的问题

//     while(true)
//     {
//         cout << "main thread !" << endl;
//         sleep(1);
//     }

//     return 0;
// }